import static java.lang.StrictMath.PI;
import static java.lang.StrictMath.abs;
import model.Game;
import model.Hockeyist;
import model.Move;
import model.Puck;
import model.Unit;
import model.World;

/**
 *
 * @author Constantine Tretyak
 */
public class Moving {

    private static final double MOVE_ANGLE = 3d * PI / 180.0D;
    private final Coords coords;
    private final Game game;
    private final Move move;
    private final Puck puck;
    private final Hockeyist self;
    private final World world;

    public Moving(Hockeyist self, World world, Game game, Move move) {
        this.self = self;
        this.world = world;
        this.game = game;
        this.move = move;
        this.puck = world.getPuck();
        this.coords = new Coords(self, world, game, move);
    }

    public boolean atSector(Unit unit, double x, double y, double sectorRadians) {
        return abs(unit.getAngleTo(x, y)) < sectorRadians / 2;
    }

    public boolean atSector(Unit self, Unit unit, double sectorRadians) {
        return atSector(self, unit.getX(), unit.getY(), sectorRadians);
    }

    public Unit cast(Unit castUnit, Unit moveTo, double a, double ticks) {
        if (castUnit instanceof Hockeyist && abs(a) > ((Hockeyist) castUnit).getStamina() / game.getHockeyistMaxStamina()) {
            a = Math.signum(a) * ((Hockeyist) castUnit).getStamina() / game.getHockeyistMaxStamina();
        }
        Unit copy = copy(castUnit, castUnit.getX(), castUnit.getY(), castUnit.getSpeedX(), castUnit.getSpeedY(), castUnit.getAngle(), castUnit.getAngularSpeed());
        double tick = 0;
        while (tick < ticks) {
            tick++;
            double maxAngle = 0;
            if (moveTo != null) {
                if (a < 0) {
                    maxAngle = 0 - copy.getAngleTo(moveTo);
                } else {
                    maxAngle = copy.getAngleTo(moveTo);
                }
                if (abs(maxAngle) > game.getHockeyistTurnAngleFactor()) {
                    maxAngle = Math.signum(maxAngle) * game.getHockeyistTurnAngleFactor();
                }
            }
            double anglarSpeed = 0;
            if (abs(copy.getAngularSpeed()) >= 0.0270190131) {
                anglarSpeed = abs(copy.getAngularSpeed()) - 0.0270190131;
                anglarSpeed *= Math.signum(copy.getAngularSpeed());
            }
            double newAngle = copy.getAngle() + maxAngle + anglarSpeed;
            double newSpeedX = (copy.getSpeedX() + Math.cos(newAngle) * (a * (a > 0 ? 12500d : 7500) / 60d / 60d / copy.getMass())) * (copy.getMass() == puck.getMass() && puck.getOwnerPlayerId() == -1 ? 0.99899996581 : 0.98d);
            double newSpeedY = (copy.getSpeedY() + Math.sin(newAngle) * (a * (a > 0 ? 12500d : 7500) / 60d / 60d / copy.getMass())) * (copy.getMass() == puck.getMass() && puck.getOwnerPlayerId() == -1 ? 0.99899996581 : 0.98d);
            if (copy.getMass() == puck.getMass()
                    && (copy.getX() < Coords.X_MIN - 30 || copy.getX() > Coords.X_MAX + 30
                    || copy.getY() < Coords.Y_MIN - 30 || copy.getY() > Coords.Y_MAX + 30)) {
                double newX = copy.getX();
                double newY = copy.getY();
                if (copy.getX() < Coords.X_MIN - 30 || copy.getX() > Coords.X_MAX + 30) {
                    newSpeedX = -newSpeedX * 0.25;
                    if (copy.getX() < Coords.X_MIN - 30) {
                        newX = Coords.X_MIN - 0.2 * (copy.getX() - Coords.X_MIN);
                    } else {
                        newX = Coords.X_MAX - 0.2 * (copy.getX() - Coords.X_MAX);
                    }
                }
                if (copy.getY() < Coords.Y_MIN - 30 || copy.getY() > Coords.Y_MAX + 30) {
                    newSpeedY = -newSpeedY * 0.25;
                    if (copy.getY() < Coords.Y_MIN - 30) {
                        newY = Coords.Y_MIN - 0.2 * (copy.getY() - Coords.Y_MIN);
                    } else {
                        newY = Coords.Y_MAX - 0.2 * (copy.getY() - Coords.Y_MAX);
                    }
                }
                copy = copy(copy, newX + newSpeedX,
                            newY + newSpeedY,
                            newSpeedX,
                            newSpeedY,
                            newAngle,
                            anglarSpeed);
            } else {
                copy = copy(copy, copy.getX() + newSpeedX,
                            copy.getY() + newSpeedY,
                            newSpeedX,
                            newSpeedY,
                            newAngle,
                            anglarSpeed);
            }
        }
        return copy;
    }

    public Unit copy(Unit unit, double x, double y, double speedX, double speedY, double angle, double angularSpeed) {
        return new Unit(unit.getId(), unit.getMass(), unit.getRadius(), x, y, speedX, speedY, angle, angularSpeed) {
        };
    }

    public double getSpeed(Unit unit) {
        return Math.hypot(unit.getSpeedX(), unit.getSpeedY());
    }

    public double getSpeedToUnit(Unit me, Unit unit) {
        double DVx = unit.getSpeedX() - me.getSpeedX();
        double DVy = unit.getSpeedY() - me.getSpeedY();
        DVx *= -Math.signum(unit.getX() - me.getX());
        DVy *= -Math.signum(unit.getY() - me.getY());
        return Math.signum(DVx * DVy) * Math.hypot(DVx, DVy);
    }

    public void moveTo(Unit unit, Unit turnTo) {
        if (unit instanceof Point) {
            if (intersection(self, unit, unit.getRadius(), 100, 0.0d, 0.0d) != -1) {
                if (turnTo == null) {
                    move.setSpeedUp(1.0d);
                } else {
                    move.setTurn(self.getAngleTo(turnTo));
                }
                return;
            }
            double bestA = 1.0d;
            double bestTicks = Double.MAX_VALUE;
            for (double a = 0d; a >= -1.0d; a -= 0.2d) {
                Unit selfCopy = cast(self, unit, a, 0);
                Unit unitCopy = cast(unit, null, 0, 0);

                double ticks = 0;
                while (ticks < 300 && abs(Math.cos(selfCopy.getAngleTo(unitCopy))) < Math.cos(turnTo != null ? MOVE_ANGLE / 6 : MOVE_ANGLE)) {
                    selfCopy = cast(selfCopy, unitCopy, a, 1);
                    unitCopy = cast(unitCopy, null, 0, 1);
                    ticks++;
                }
                if (ticks >= 300) {
                    continue;
                }
                while (ticks < 600 && selfCopy.getDistanceTo(unitCopy) > unitCopy.getRadius() + (turnTo == null ? self.getRadius() : 0)) {
                    selfCopy = cast(selfCopy, unitCopy, Math.signum(a) * 1.0d, 1);
                    unitCopy = cast(unitCopy, null, 0, 1);
                    ticks++;
                }
                if (ticks <= bestTicks) {
                    bestTicks = ticks;
                    bestA = a;
                }
            }
            for (double a = 0d; a <= 1.0d; a += 0.2d) {
                Unit selfCopy = cast(self, unit, a, 0);
                Unit unitCopy = cast(unit, null, 0, 0);

                double ticks = 0;
                while (ticks < 300 && abs(Math.cos(selfCopy.getAngleTo(unitCopy))) < Math.cos(turnTo != null ? MOVE_ANGLE / 6 : MOVE_ANGLE)) {
                    selfCopy = cast(selfCopy, unitCopy, a, 1);
                    unitCopy = cast(unitCopy, null, 0, 1);
                    ticks++;
                }
                if (ticks >= 300) {
                    continue;
                }
                while (ticks < 600 && selfCopy.getDistanceTo(unitCopy) > unitCopy.getRadius()) {
                    selfCopy = cast(selfCopy, unitCopy, Math.signum(a) * 1.0d, 1);
                    unitCopy = cast(unitCopy, null, 0, 1);
                    ticks++;
                }
                if (ticks <= bestTicks) {
                    bestTicks = ticks;
                    bestA = a;
                }
            }

            Unit futureUnit = cast(unit, null, 0.0d, bestTicks);
            move.setSpeedUp(bestA);
            move.setTurn(bestA < 0 ? Math.signum(self.getAngleTo(futureUnit)) * PI - self.getAngleTo(futureUnit) : self.getAngleTo(futureUnit));
        } else {

            double movingTicks = 0;
            while (movingTicks < 100) {
                Unit unitFromMt = cast(unit, null, 0.0d, movingTicks);
                for (double a = 1.0d; a >= -1.0d; a -= 0.05d) {
                    Unit seflFromMt = cast(self, unitFromMt, a, movingTicks);
                    if (seflFromMt.getDistanceTo(unitFromMt) > seflFromMt.getRadius() / 2 && seflFromMt.getDistanceTo(unitFromMt) < seflFromMt.getRadius() + unitFromMt.getRadius() && atSector(seflFromMt, unitFromMt, game.getStickSector() * 0.9d)) {
                        move.setSpeedUp(a);
                        move.setTurn(a < 0 ? Math.signum(self.getAngleTo(unitFromMt)) * PI - self.getAngleTo(unitFromMt) : self.getAngleTo(unitFromMt));
                        return;
                    }
                }
                movingTicks += 5;
            }

            move.setSpeedUp(1.0d);
            move.setTurn(self.getAngleTo(unit));
        }
    }

    private double intersection(Unit unit1, Unit unit2, double radius, double maxTicks, double a1, double a2) {
        double tick = 0;
        Unit copy1 = cast(unit1, null, a1, tick);
        Unit copy2 = cast(unit2, null, a2, tick);

        do {
            if (copy1.getDistanceTo(copy2) < radius) {
                return tick;
            }
            copy1 = cast(copy1, copy2, a1, 1);
            copy2 = cast(copy2, null, a2, 1);
            tick++;
        } while (tick <= maxTicks);
        return -1;
    }
}
